<?php

namespace Hesb\LaravelPlus\Middlewares;

use Closure;
use Hesb\LaravelPlus\AnnotationCollector;
use Hesb\LaravelPlus\AnnotationReader;
use Hesb\LaravelPlus\Container;
use Hesb\LaravelPlus\Exception\InvalidArgumentException;
use Hesb\LaravelPlus\Mapper;
use Hesb\LaravelPlus\MethodDefinitionCollector;
use Hesb\LaravelPlus\Scan\MethodParametersManager;
use Hesb\LaravelPlus\Scan\ValidationManager;
use Hesb\LaravelPlus\Validate\ValidationDto;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\Routing\ResolvesRouteDependencies;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use ReflectionAttribute;
use ReflectionException;
use ReflectionMethod;
use Symfony\Component\HttpFoundation\Exception\JsonException;
use Zenith\LaravelPlus\Attributes\Validators\Param;
use Zenith\LaravelPlus\Exceptions\ValidatedErrorException;

class HttpValidation
{
//    use  ResolvesRouteDependencies;

    /**
     * Handle an incoming request.
     *
     * @param Closure(Request): mixed $next
     * @throws ReflectionException
     * @throws ValidatedErrorException
     */
    public function handle(Request $request, Closure $next): mixed
    {
        $controller = $request->route()->getControllerClass();
        $action = $request->route()->getActionMethod();


        $methodDefinitionCollector = app(MethodDefinitionCollector::class);
        $definitions = $methodDefinitionCollector->getParameters($controller, $action);

        $this->getInjections($request,$definitions, "{$controller}::{$action}", []);

        //        $reflectionMethod = new ReflectionMethod($controller, $action);
//        $params = collect($reflectionMethod->getAttributes(Param::class))
//            ->map(function (ReflectionAttribute $attribute) {
//                $instance = $attribute->newInstance();
//                $rules = explode('|', $attribute->newInstance()->rules);
//                foreach ($rules as &$rule) {
//                    if (class_exists($rule)) {
//                        $rule = new $rule();
//                    }
//                }
//                $isContainRequiredRule = ! collect($rules)
//                    ->filter(fn ($rule) => is_string($rule))
//                    ->filter(fn ($rule) => str_contains($rule, 'required'))
//                    ->isEmpty();
//                if (! $isContainRequiredRule && $instance->required) {
//                    $rules[] = 'required';
//                }
//                return [
//                    'key' => $instance->key,
//                    'rule' => $rules,
//                    'message' => $instance->message,
//                    'required' => $instance->required,
//                ];
//            });
//        $rules = $keys = $messages = [];
//        foreach ($params as $param) {
//            $keys[] = $param['key'];
//            $rules[$param['key']] = $param['rule'];
//            // Multiple language support.
//            if (str_starts_with($param['message'], 'trans:')) {
//                $key = substr($param['message'], strlen('trans:'));
//                $messages[$param['key']] = trans($key) ?? $param['message'];
//            } else {
//                $messages[$param['key']] = $param['message'];
//            }
//        }
//        $routeParameters = $request->route()->parameters();
//        $parameters = array_merge($routeParameters, $request->all());
//        $validator = Validator::make($parameters, $rules, $messages);
//        if ($validator->stopOnFirstFailure()->fails()) {
//            throw new ValidatedErrorException($validator->errors()->first());
//        }

        return $next($request);
    }

    private function getInjections(Request $request,array $definitions, string $callableName, array $arguments): array
    {
        $injections = [];
        foreach ($definitions ?? [] as $pos => $definition) {
            $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null;
            if ($value === null) {
                if ($definition->getMeta('defaultValueAvailable')) {
                    $injections[] = $definition->getMeta('defaultValue');
                } elseif ($definition->allowsNull()) {
                    $injections[] = null;
                } elseif (Container::getInstance()->has($definition->getName())) {
                    $obj = Container::getInstance()->get($definition->getName());
                    $injections[] = $this->validateAndMap($request,$callableName, $definition->getMeta('name'), $definition->getName(), $obj);
                } else {
                    throw new InvalidArgumentException("Parameter '{$definition->getMeta('name')}' "
                        . "of {$callableName} should not be null");
                }
            } else {
                $injections[] = $this->container->get(NormalizerInterface::class)->denormalize($value, $definition->getName());
            }
        }

        return $injections;
    }


    /**
     * @param string $callableName
     * @param string $paramName
     * @param string $className
     * @param mixed $obj
     * @return mixed
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    private function validateAndMap(Request $request,string $callableName, string $paramName, string $className, mixed $obj): mixed
    {
        [$controllerName, $methodName] = explode('::', $callableName);
        $methodParameter = MethodParametersManager::getMethodParameter($controllerName, $methodName, $paramName);
        if ($methodParameter === null) {
            return $obj;
        }
        $validationDTO = make(ValidationDto::class);
        $param = [];
        if ($methodParameter->isRequestBody()) {
//            $param = $request->input();
            $param = $this->getParseBody();
        } elseif ($methodParameter->isRequestQuery()) {
            $param = $_GET;
        } elseif ($methodParameter->isRequestFormData()) {
            $param = $this->getParseBody();
            $files = $this->normalizeFiles($_FILES);
            // 兼容file
            if (is_array($files)) {
                $param = array_merge($param, $files);
            }
        } elseif ($methodParameter->isRequestHeader()) {
            $param = array_map(static function ($value) {
                return $value[0];
            }, $request->getHeaders());
        }
        // validate
        if ($methodParameter->isValid()) {
            $validationDTO->validate($className, $param);
        }
        //设置参数
        $request->route()->parameters = [$obj];
        //var_dump("mapper",);
//        if (PropertyAliasMappingManager::isAliasMapping()) {
//            return Mapper::mapDto($param, make($className));
//        }
        return Mapper::map($param, make($className));
    }


    /**
     * 获取body数组
     *
     */
    public  function getParseBody(): array
    {
        //form和application/x-www-form-urlencoded
        $param = $_POST;
        if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'application/json') {
            //请求的内容类型是JSON
            $content = file_get_contents('php://input');
            try {
                $param = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
            } catch (\JsonException $e) {
                throw new JsonException('Could not decode request body.', $e->getCode(), $e);
            }
        }
        return $param;
    }

    /**
     * Return an UploadedFile instance array.
     *
     * @param array $files An array which respect $_FILES structure
     *
     * @throws InvalidArgumentException for unrecognized values
     */
    public  function normalizeFiles(array $files): array
    {
        $normalized = [];

        foreach ($files as $key => $value) {
            if (is_array($value) && isset($value['tmp_name'])) {
                $normalized[$key] = new UploadedFile(
                    $value['tmp_name'],
                    $value['name'],
                    $value['type'],
                    (int) $value['error'],
                );
            } else {
                throw new InvalidArgumentException(400,'Invalid value in files specification');
            }
        }

        return $normalized;
    }
}
